/******************************************************************************* * * Copyright (c) 2012 GigaSpaces Technologies Ltd. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.openspaces.jpa; import java.lang.reflect.Method; import java.rmi.RemoteException; import java.sql.SQLException; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Stack; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import net.jini.core.entry.UnusableEntryException; import net.jini.core.lease.Lease; import net.jini.core.transaction.Transaction; import net.jini.core.transaction.TransactionException; import net.jini.core.transaction.TransactionFactory; import org.apache.openjpa.abstractstore.AbstractStoreManager; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.kernel.FetchConfiguration; import org.apache.openjpa.kernel.LockManager; import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.PCState; import org.apache.openjpa.kernel.QueryLanguages; import org.apache.openjpa.kernel.StateManager; import org.apache.openjpa.kernel.StoreQuery; import org.apache.openjpa.kernel.exps.ExpressionParser; import org.apache.openjpa.lib.rop.ResultObjectProvider; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.util.ApplicationIds; import org.openspaces.jpa.openjpa.SpaceConfiguration; import org.openspaces.jpa.openjpa.StoreManagerQuery; import org.openspaces.jpa.openjpa.StoreManagerSQLQuery; import com.gigaspaces.annotation.pojo.SpaceId; import com.gigaspaces.internal.client.QueryResultTypeInternal; import com.gigaspaces.internal.client.spaceproxy.ISpaceProxy; import com.gigaspaces.internal.client.spaceproxy.metadata.ObjectType; import com.gigaspaces.internal.metadata.ITypeDesc; import com.gigaspaces.internal.metadata.SpaceTypeInfo; import com.gigaspaces.internal.metadata.SpaceTypeInfoRepository; import com.gigaspaces.internal.transport.IEntryPacket; import com.gigaspaces.internal.transport.ITemplatePacket; import com.gigaspaces.internal.transport.TemplatePacketFactory; import com.j_spaces.core.IJSpace; import com.j_spaces.core.client.ReadModifiers; import com.j_spaces.core.client.UpdateModifiers; import com.j_spaces.jdbc.QueryProcessorFactory; import com.j_spaces.jdbc.driver.GConnection; /** * A GigaSpaces back-end implementation for OpenJPA. * Responsible for storing and fetching data from GigaSpaces using space API. * * @author idan * @since 8.0 * */ @SuppressWarnings("unchecked") public class StoreManager extends AbstractStoreManager { // private Transaction _transaction = null; private static final Map<Class<?>, Integer> _classesRelationStatus = new HashMap<Class<?>, Integer>(); private static final HashSet<Class<?>> _processedClasses = new HashSet<Class<?>>(); private GConnection _connection; private RelationsManager _relationsManager; public StoreManager() { _relationsManager = new RelationsManager(); } @Override protected void open() { // Specific gigaspaces initialization (space proxy) getConfiguration().initialize(); } @Override protected Collection<String> getUnsupportedOptions() { Collection<String> unsupportedOptions = super.getUnsupportedOptions(); unsupportedOptions.remove(OpenJPAConfiguration.OPTION_ID_DATASTORE); unsupportedOptions.remove(OpenJPAConfiguration.OPTION_OPTIMISTIC); unsupportedOptions.remove(OpenJPAConfiguration.OPTION_INC_FLUSH); return unsupportedOptions; } @Override public boolean syncVersion(OpenJPAStateManager sm, Object edata) { try { // If there's no version field return false // Object will be loaded from space by OpenJPA if (!getConfiguration().getOptimistic() || sm.getMetaData().getVersionField() == null) return false; // Verify version IEntryPacket result = readObjectFromSpace(sm); if (result == null) return false; Object spaceVersion = result.getVersion(); if (spaceVersion == null) throw new IllegalStateException("Entity of type: " + result.getTypeName() + " with Id: " + result.getID() + " expected to have a value in its version property."); return sm.getVersion().equals(spaceVersion); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } @Override public void begin() { try { if (_transaction != null) { if(getConfiguration().getOptimistic()) return; throw new TransactionException("Attempted to start a new transaction when there's already an active transaction."); } long timeout = (getConfiguration().getLockTimeout() == 0)? Lease.FOREVER : getConfiguration().getLockTimeout(); _transaction = (TransactionFactory.create(getConfiguration().getTransactionManager(), timeout)).transaction; } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } @Override public void commit() { try { long timeout = (getConfiguration().getLockTimeout() == 0)? Lease.FOREVER : getConfiguration().getLockTimeout(); _transaction.commit(timeout); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { _transaction = null; } } @Override public void rollback() { try { _transaction.abort(Long.MAX_VALUE); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { _transaction = null; } } @Override public void beginOptimistic() { // Do nothing... (a transaction for rollback purpose will be started on flush) } @Override public void rollbackOptimistic() { if (_transaction != null) rollback(); } @Override public StoreQuery newQuery(String language) { ExpressionParser ep = QueryLanguages.parserForLanguage(language); if(ep != null) return new StoreManagerQuery(ep, this); if (QueryLanguages.LANG_SQL.equals(language)) { return new StoreManagerSQLQuery(this); } return null; } @Override protected OpenJPAConfiguration newConfiguration() { return new SpaceConfiguration(); } public SpaceConfiguration getConfiguration() { return (SpaceConfiguration) getContext().getConfiguration(); } /** * Returns whether the state manager's managed object exists in space. */ public boolean exists(OpenJPAStateManager sm, Object edata) { ClassMetaData cm = sm.getMetaData(); final Object[] ids = ApplicationIds.toPKValues(sm.getObjectId(), cm); ISpaceProxy proxy = (ISpaceProxy) getConfiguration().getSpace(); try { Object result = proxy.readById(cm.getDescribedType().getName(), ids[0], null, _transaction, 0, ReadModifiers.DIRTY_READ, false, QueryResultTypeInternal.EXTERNAL_ENTRY, null); return result != null; } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } public boolean isCached(List<Object> oids, BitSet edata) { return false; } @SuppressWarnings("rawtypes") @Override public Collection loadAll(Collection sms, PCState state, int load, FetchConfiguration fetch, Object edata) { final List<Object> failedIds = new ArrayList<Object>(); for (OpenJPAStateManager sm : (Collection<OpenJPAStateManager>) sms) { BitSet fields = new BitSet(sm.getMetaData().getFields().length); LockManager lm = sm.getContext().getLockManager(); if (!load(sm, fields, fetch, lm.getLockLevel(sm), edata)) failedIds.add(sm.getId()); } return failedIds; } @Override public boolean initialize(OpenJPAStateManager sm, PCState state, FetchConfiguration fetchConfiguration, Object edata) { final ClassMetaData cm = sm.getMetaData(); try { // If we already have the result and only need to initialize.. (relevant for nested objects & JPQL) IEntryPacket result = (edata == null) ? readObjectFromSpace(sm) : (IEntryPacket) edata; if (result == null) return false; // Initialize sm.initialize(cm.getDescribedType(), state); loadFields(sm, result, cm.getFields()); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } return true; } /** * Loads the provided IEntryPacket field values to the provided StateManager. * Note that for gaining better performance OneToOne & OneToMany relationships are loaded but * not initialized (lazy initialization). * * @param sm The state manager. * @param entry The IEntryPacket containing the field values. * @param fms The fields meta data. */ private void loadFields(OpenJPAStateManager sm, IEntryPacket entry, FieldMetaData[] fms) { int spacePropertyIndex = -1; for (int i = 0; i < fms.length; i++) { //ignore version which is not part of the entry packet if(fms[i].isVersion()) continue; spacePropertyIndex++; // Skip primary keys and non-persistent keys if (fms[i].isPrimaryKey() || sm.getLoaded().get(fms[i].getIndex())) continue; Integer associationType = _classesRelationStatus.get(fms[i].getElement().getDeclaredType()); if (associationType != null) fms[i].setAssociationType(associationType); // Handle one-to-one if (fms[i].getAssociationType() == FieldMetaData.ONE_TO_ONE) { sm.store(i, entry.getFieldValue(spacePropertyIndex)); sm.getLoaded().set(fms[i].getIndex(), false); // Handle one-to-many } else if (fms[i].getAssociationType() == FieldMetaData.ONE_TO_MANY) { sm.store(i, entry.getFieldValue(spacePropertyIndex)); sm.getLoaded().set(fms[i].getIndex(), false); // Handle embedded property } else if (fms[i].isEmbeddedPC()) { loadEmbeddedObject(fms[i], sm, entry.getFieldValue(spacePropertyIndex)); // Otherwise, store the value as is } else { sm.store(i, entry.getFieldValue(spacePropertyIndex)); } } sm.setVersion(entry.getVersion()); ((StateManager) sm).resetClearedState(); } /** * Loads a One-to-one relationship object to the provided owner's state manager. * @param fmd The owner's field meta data. * @param sm The owner's state manager. * @param fieldValue The One-to-one field value to load into the owner's state manager. */ private void loadOneToOneObject(FieldMetaData fmd, OpenJPAStateManager sm, Object fieldValue) { if (fieldValue == null) { sm.storeObject(fmd.getIndex(), null); } else { final ISpaceProxy proxy = (ISpaceProxy) getConfiguration().getSpace(); final IEntryPacket entry = proxy.getDirectProxy().getTypeManager().getEntryPacketFromObject( fieldValue, ObjectType.POJO); final ClassMetaData cmd = fmd.getDeclaredTypeMetaData(); final Object oid = ApplicationIds.fromPKValues(new Object[] { entry.getID() }, cmd); final BitSet exclude = new BitSet(cmd.getFields().length); final Object managedObject = getContext().find(oid, null, exclude, entry, 0); _relationsManager.setOwnerStateManagerForPersistentInstance(managedObject, sm, fmd); sm.storeObject(fmd.getIndex(), managedObject); } } /** * Loads an embedded object field. * @param fmd The embedded field meta data. * @param sm The parent object state manager. * @param fieldValue The value to load for the embedded field. */ private void loadEmbeddedObject(FieldMetaData fmd, OpenJPAStateManager sm, Object fieldValue) { if (fieldValue == null) { sm.storeObject(fmd.getIndex(), null); } else { if (fieldValue != null) { final OpenJPAStateManager em = ctx.embed(null, null, sm, fmd); ((StateManager) em).setOwnerInformation((StateManager) sm, fmd); sm.storeObject(fmd.getIndex(), em.getManagedInstance()); final ISpaceProxy proxy = (ISpaceProxy) getConfiguration().getSpace(); final IEntryPacket entry = proxy.getDirectProxy().getTypeManager().getEntryPacketFromObject( fieldValue, ObjectType.POJO); loadFields(em, entry, fmd.getDeclaredTypeMetaData().getFields()); } } } /** * Loads One-to-many relationship objects to the owner's state manager. * * @param fmd The One-to-many field's meta data. * @param sm The owner's state manager. * @param fieldValue The value to be stored for the current field. */ private void loadOneToManyObjects(FieldMetaData fmd, OpenJPAStateManager sm, Object fieldValue) { final Object collection = sm.newProxy(fmd.getIndex()); if (fieldValue != null) { final ISpaceProxy proxy = (ISpaceProxy) getConfiguration().getSpace(); final ClassMetaData cmd = fmd.getElement().getDeclaredTypeMetaData(); final BitSet exclude = new BitSet(cmd.getFields().length); // Initialize each of the collection's items for (Object item : (Collection<?>) fieldValue) { final IEntryPacket entry = proxy.getDirectProxy().getTypeManager().getEntryPacketFromObject( item, ObjectType.POJO); final Object oid = ApplicationIds.fromPKValues(new Object[] { entry.getID() }, cmd); // Initialize a state manager for the current item final Object managedObject = getContext().find(oid, null, exclude, entry, 0); _relationsManager.setOwnerStateManagerForPersistentInstance(managedObject, sm, fmd); ((Collection<Object>) collection).add(managedObject); } } sm.storeObject(fmd.getIndex(), collection); } /** * Reads an IEntryPacket implementation from space according to the provided StateManager. * @param sm The state manager. * @return The IEntryPacket implementation for the provided StateManager. */ private IEntryPacket readObjectFromSpace(OpenJPAStateManager sm) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException { IEntryPacket result; final ISpaceProxy proxy = (ISpaceProxy) getConfiguration().getSpace(); final ITypeDesc typeDescriptor = proxy.getDirectProxy().getTypeManager().getTypeDescByName( sm.getMetaData().getDescribedType().getName()); final Object[] ids = ApplicationIds.toPKValues(sm.getObjectId(), sm.getMetaData()); final int readModifier = (_transaction != null)? getConfiguration().getReadModifier() : ReadModifiers.REPEATABLE_READ; ITemplatePacket template; if (typeDescriptor.isAutoGenerateId()) template = TemplatePacketFactory.createUidPacket((String) ids[0], null, 0, QueryResultTypeInternal.OBJECT_JAVA); else template = TemplatePacketFactory.createIdPacket(ids[0], null, 0, typeDescriptor, QueryResultTypeInternal.OBJECT_JAVA, null); result = (IEntryPacket) proxy.read(template, _transaction, 0, readModifier); return result; } /** * This method loads specific fields from the data store for updating them. * Note: The state manager's fields are cleared. */ @Override public boolean load(OpenJPAStateManager sm, BitSet fields, FetchConfiguration fetch, int lockLevel, Object context) { final ClassMetaData cm = sm.getMetaData(); final StateManager stateManager = (StateManager) sm; final SpaceTypeInfo typeInfo = SpaceTypeInfoRepository.getTypeInfo(cm.getDescribedType()); final StateManager gsm = (StateManager) sm; try { if (!gsm.isCleared()) { loadSpecificFields(sm, fields, typeInfo); return true; } else { // If this is a relationship owner, read object from space and lazy initialize its fields // And initialize the fields specified in the provided 'fields' BitSet if (stateManager.getOwnerStateManager() == null) { final IEntryPacket entry = readObjectFromSpace(sm); if (entry == null) return false; loadFields(sm, entry, cm.getFields()); loadSpecificFields(sm, fields, typeInfo); return true; // If this is an owned instance (Owner->Pet), read owner from space and find // the instance according to its Id and load & initialize its fields. } else { // Save route to owner state manager Stack<StateManager> sms = new Stack<StateManager>(); StateManager stateManagerToRead = stateManager; while (stateManagerToRead.getOwnerStateManager() != null) { sms.push(stateManagerToRead); stateManagerToRead = stateManagerToRead.getOwnerStateManager(); } final IEntryPacket entry = readObjectFromSpace(stateManagerToRead); if (entry == null) return false; // Find the desired instance final IEntryPacket foundEntryPacket = _relationsManager.findObjectInEntry(stateManagerToRead, entry, sms); if (foundEntryPacket == null) return false; loadFields(sm, foundEntryPacket, sm.getMetaData().getFields()); loadSpecificFields(sm, fields, typeInfo); } return true; } } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } /** * Load the fields specified in the provided 'fields' BitSet. * * @param sm The state manager to load fields for. * @param fields The fields to load. * @param typeInfo {@link SpaceTypeInfo} used for reflection. */ private void loadSpecificFields(OpenJPAStateManager sm, BitSet fields, final SpaceTypeInfo typeInfo) { for (FieldMetaData fmd : sm.getMetaData().getFields()) { if (fields.get(fmd.getIndex())) { Object instance = sm.getManagedInstance(); // Remove state manager before using reflection ((PersistenceCapable) instance).pcReplaceStateManager(null); Object value = typeInfo.getProperty(fmd.getName()).getValue(sm.getManagedInstance()); ((PersistenceCapable) instance).pcReplaceStateManager(sm); if (fmd.getAssociationType() == FieldMetaData.ONE_TO_MANY) { loadOneToManyObjects(fmd, sm, value); } else if (fmd.getAssociationType() == FieldMetaData.ONE_TO_ONE) { loadOneToOneObject(fmd, sm, value); } else if (fmd.isEmbeddedPC()) { loadEmbeddedObject(fmd, sm, value); } } } } @Override public ResultObjectProvider executeExtent(ClassMetaData classmetadata, boolean flag, FetchConfiguration fetchconfiguration) { return null; } /** * Flushes changes to GigaSpaces. * Returns a list of exceptions that occurred. */ @SuppressWarnings({ "rawtypes" }) @Override protected Collection flush(Collection pNew, Collection pNewUpdated, Collection pNewFlushedDeleted, Collection pDirty, Collection pDeleted) { if (getContext().getBroker().getOptimistic() && _transaction == null) begin(); IJSpace space = getConfiguration().getSpace(); ArrayList<Exception> exceptions = new ArrayList<Exception>(); if (_relationsManager.shouldInitializeClassesRelationStatus()) _relationsManager.initializeClassesRelationStatus(); if (pNew.size() > 0) handleNewObjects(pNew, space); if (pDirty.size() > 0) handleUpdatedObjects(pDirty, exceptions, space); if (pDeleted.size() > 0) handleDeletedObjects(pDeleted, exceptions, space); return exceptions; } /** * Clears the removed objects from the space. */ private void handleDeletedObjects(Collection<OpenJPAStateManager> sms, ArrayList<Exception> exceptions, IJSpace space) { for (OpenJPAStateManager sm : sms) { ClassMetaData cm = sm.getMetaData(); if (_classesRelationStatus.containsKey(cm.getDescribedType())) continue; try { // Remove object from space final Object[] ids = ApplicationIds.toPKValues(sm.getObjectId(), cm); final ISpaceProxy proxy = (ISpaceProxy) space; final ITypeDesc typeDescriptor = proxy.getDirectProxy().getTypeManager().getTypeDescByName(sm.getMetaData().getDescribedType().getName()); final Object routing = sm.fetch(typeDescriptor.getRoutingPropertyId()); ITemplatePacket template; if (typeDescriptor.isAutoGenerateId()) template = TemplatePacketFactory.createUidPacket((String) ids[0], routing, 0, QueryResultTypeInternal.OBJECT_JAVA); else template = TemplatePacketFactory.createIdPacket(ids[0], routing, 0, typeDescriptor, QueryResultTypeInternal.OBJECT_JAVA, null); int result = proxy.clear(template, _transaction, 0); if (result != 1) throw new Exception("Unable to clear object from space."); } catch (Exception e) { exceptions.add(e); } } } /** * Partially updates dirty fields to the space. */ private void handleUpdatedObjects(Collection<OpenJPAStateManager> sms, ArrayList<Exception> exceptions, IJSpace space) { // Generate a template for each state manager and use partial update for updating.. HashSet<OpenJPAStateManager> stateManagersToRestore = new HashSet<OpenJPAStateManager>(); for (OpenJPAStateManager sm : sms) { final ClassMetaData cm = sm.getMetaData(); try { // Find relationship owner and flush it to space if (_classesRelationStatus.containsKey(cm.getDescribedType())) { final FieldOwnerInformation ownerInformation = _relationsManager.getStateManagerToUpdate((StateManager) sm); final IEntryPacket entry = getEntryPacketFromStateManager(space, ownerInformation.getStateManager()); // Write changes to the space for (FieldMetaData fmd : cm.getFields()) { _relationsManager.initializeOwnerReferencesForField((StateManager) sm, fmd); } _relationsManager.removeOwnedEntitiesStateManagers(stateManagersToRestore, ownerInformation.getStateManager()); if (ownerInformation.getStateManager().getVersion() != null) entry.setVersion((Integer) ownerInformation.getStateManager().getVersion()); final FieldMetaData[] fmds = ownerInformation.getStateManager().getMetaData().getFields(); int spacePropertyIndex = -1; int routingPropertyIndex = entry.getTypeDescriptor().getRoutingPropertyId(); for (int i = 0; i < fmds.length; i++) { //ignore version which is not part of the entry packet if(fmds[i].isVersion()) continue; spacePropertyIndex++; if (i != ownerInformation.getMetaData().getIndex() && !fmds[i].isPrimaryKey() && spacePropertyIndex != routingPropertyIndex) { entry.setFieldValue(spacePropertyIndex, null); } } space.write(entry, _transaction, Lease.FOREVER, 0, UpdateModifiers.PARTIAL_UPDATE); //update the version ownerInformation.getStateManager().setVersion(entry.getVersion()); } else { // Create an entry packet from the updated POJO and set all the fields // but the updated & primary key to null. final IEntryPacket entry = getEntryPacketFromStateManager(space, sm); final FieldMetaData[] fmds = cm.getFields(); int spacePropertyIndex = -1; int routingPropertyIndex = entry.getTypeDescriptor().getRoutingPropertyId(); for (int i = 0; i < fmds.length; i++) { //ignore version which is not part of the entry packet if(fmds[i].isVersion()) continue; spacePropertyIndex++; if (!sm.getDirty().get(i) && !fmds[i].isPrimaryKey() && spacePropertyIndex != routingPropertyIndex) { entry.setFieldValue(spacePropertyIndex, null); } else { _relationsManager.initializeOwnerReferencesForField((StateManager) sm, fmds[i]); } } if(sm.getVersion() != null) entry.setVersion((Integer) sm.getVersion()); // Write changes to the space space.write(entry, _transaction, Lease.FOREVER, 0, UpdateModifiers.PARTIAL_UPDATE); //update the version sm.setVersion(entry.getVersion()); } } catch (Exception e) { exceptions.add(e); } finally { _relationsManager.restoreRemovedStateManagers(stateManagersToRestore); } } } /** * Gets an {@link IEntryPacket} instance from the provided {@link OpenJPAStateManager}'s managed instance. * The conversion is made after removing the state manager from the managed object because its fields * are accessed using reflection and this might cause problems due to OpenJPA's entities enhancement. * * @param space Space instance the conversion will be called for (using its type manager). * @param sm The state manager whose managed object will be converted. * @return An {@link IEntryPacket} instance representing the managed object. */ private IEntryPacket getEntryPacketFromStateManager(IJSpace space, OpenJPAStateManager sm) { try { final ISpaceProxy proxy = (ISpaceProxy) space; sm.getPersistenceCapable().pcReplaceStateManager(null); IEntryPacket entry = proxy.getDirectProxy().getTypeManager().getEntryPacketFromObject( sm.getManagedInstance(), ObjectType.POJO); return entry; } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { sm.getPersistenceCapable().pcReplaceStateManager(sm); } } /** * Converts the provided {@link Entity} to an {@link IEntryPacket} instance. * The conversion is made after removing the entity's state manager (if it exists) and returning it * after the conversion is made due to conflicts between reflection and OpenJPA's enhancement. * * @param entity The {@link Entity} to convert. * @return An {@link IEntryPacket} instance representing the provided entity. */ private IEntryPacket getEntryPacketFromEntity(Object entity) { PersistenceCapable pc = (PersistenceCapable) entity; final ISpaceProxy proxy = (ISpaceProxy) getConfiguration().getSpace(); IEntryPacket entry; if (pc.pcGetStateManager() != null) { OpenJPAStateManager sm = (OpenJPAStateManager) pc.pcGetStateManager(); try { pc.pcReplaceStateManager(null); entry = proxy.getDirectProxy().getTypeManager().getEntryPacketFromObject( entity, ObjectType.POJO); } finally { pc.pcReplaceStateManager(sm); } } else { entry = proxy.getDirectProxy().getTypeManager().getEntryPacketFromObject( entity, ObjectType.POJO); } return entry; } /** * Writes new persistent objects to the space. */ private void handleNewObjects(Collection<OpenJPAStateManager> sms, IJSpace space) { final HashMap<Class<?>, ArrayList<Object>> objectsToWriteByType = new HashMap<Class<?>, ArrayList<Object>>(); final ArrayList<OpenJPAStateManager> stateManagersToRestore = new ArrayList<OpenJPAStateManager>(); Class<?> previousType = null; ArrayList<Object> currentList = null; for (OpenJPAStateManager sm : sms) { // If the current object is in a relation skip it if (_classesRelationStatus.containsKey(sm.getMetaData().getDescribedType())) { continue; } // If the object has managed instances in its fields we need to remove the state manager from these instances // since they are serialized when written to space and can cause a deadlock when written // by writeMultiple. _relationsManager.removeOwnedEntitiesStateManagers(stateManagersToRestore, sm); // In order to use writeMultiple we need to gather each type's instances to its own list if (!sm.getMetaData().getDescribedType().equals(previousType)) { currentList = objectsToWriteByType.get(sm.getMetaData().getDescribedType()); if (currentList == null) { currentList = new ArrayList<Object>(); objectsToWriteByType.put(sm.getMetaData().getDescribedType(), currentList); } previousType = sm.getMetaData().getDescribedType(); } // Each persisted class should have its state manager removed // before being written to space since gigaspaces reflection conflicts with // OpenJPA's class monitoring. sm.getPersistenceCapable().pcReplaceStateManager(null); stateManagersToRestore.add(sm); currentList.add(sm.getManagedInstance()); } // Write objects to space in batches by type try { for (Map.Entry<Class<?>, ArrayList<Object>> entry : objectsToWriteByType.entrySet()) { space.writeMultiple(entry.getValue().toArray(), _transaction, Lease.FOREVER, UpdateModifiers.WRITE_ONLY); } } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { // Restore the removed state managers. _relationsManager.restoreRemovedStateManagers(stateManagersToRestore); } // If optimistic locking is enabled, set version for written states // (The version is already saved in its property but not in the states version field) if (getConfiguration().getOptimistic()) { for (Map.Entry<Class<?>, ArrayList<Object>> entry : objectsToWriteByType.entrySet()) { for (Object obj : entry.getValue()) { PersistenceCapable pc = (PersistenceCapable) obj; OpenJPAStateManager sm = (OpenJPAStateManager) pc.pcGetStateManager(); if (sm.getMetaData().getVersionField() == null) break; Object version = sm.fetch(sm.getMetaData().getVersionField().getIndex()); sm.setVersion(version); } } } } /** * Validates the provided class' annotations. * Currently the only validation performed is for @Id & @SpaceId annotations * that must be declared on the same getter. */ private void validateClassAnnotations(Class<?> type) { // Validation is only relevant for Entities if (type.getAnnotation(Entity.class) == null) return; for (Method getter : type.getMethods()) { if (!getter.getName().startsWith("get")) continue; SpaceId spaceId = getter.getAnnotation(SpaceId.class); boolean hasJpaId = getter.getAnnotation(Id.class) != null || getter.getAnnotation(EmbeddedId.class) != null; if (spaceId != null || hasJpaId) { if (!hasJpaId || spaceId == null) throw new IllegalArgumentException("SpaceId and Id annotations must both be declared on the same property in JPA entities in type: " + type.getName()); if (spaceId.autoGenerate()) { GeneratedValue generatedValue = getter.getAnnotation(GeneratedValue.class); if (generatedValue == null || generatedValue.strategy() != GenerationType.IDENTITY) throw new IllegalArgumentException( "SpaceId with autoGenerate=true annotated property should also have a JPA GeneratedValue annotation with strategy = GenerationType.IDENTITY."); } break; } } } /** * Initializes an ExternalEntry result as a state managed Pojo. * (used by JPQL's query executor) */ public Object loadObject(ClassMetaData classMetaData, IEntryPacket entry) { // Get object id Object[] ids = new Object[1]; ids[0] = entry.getID(); Object objectId = ApplicationIds.fromPKValues(ids, classMetaData); return getContext().find(objectId, null, null, entry, 0); } /** * Gets the current active transaction. */ public Transaction getCurrentTransaction() { return _transaction; } /** * Gets a JDBC connection using the configuration's space instance. * Each store manager has its own Connection for Multithreaded reasons. */ public GConnection getJdbcConnection() throws SQLException { if (_connection == null) { Properties connectionProperties = new Properties(); connectionProperties.put(QueryProcessorFactory.COM_GIGASPACES_EMBEDDED_QP_ENABLED, "true"); _connection = GConnection.getInstance(getConfiguration().getSpace(), connectionProperties); if (_connection.getAutoCommit()) _connection.setAutoCommit(false); } return _connection; } /** * Gets the class relation status (one-to-one etc..) for the provided type. */ public synchronized int getClassRelationStatus(Class<?> type) { // In case relations status was not initialized already.. if (_relationsManager.shouldInitializeClassesRelationStatus()) _relationsManager.initializeClassesRelationStatus(); // Get relation status.. Integer relationStatus = _classesRelationStatus.get(type); return (relationStatus == null) ? FieldMetaData.MANAGE_NONE : relationStatus; } /** * Keeps information for a field's owner. */ private static class FieldOwnerInformation { private StateManager stateManager; private FieldMetaData metaData; public FieldOwnerInformation(StateManager stateManager, FieldMetaData metaData) { this.stateManager = stateManager; this.metaData = metaData; } public StateManager getStateManager() { return stateManager; } public FieldMetaData getMetaData() { return metaData; } } /** * StoreManager's relationships manager. * Provides methods for handling relationships in GigaSpaces owned relationships model. */ private class RelationsManager { public RelationsManager() { } /** * Removes owned entities state managers (before writing them to space due to serialization deadlock problem). * The removed state managers are kept in the provided collection for restoring them later. * * @param stateManagersToRestore The collection for storing the removed state managers. * @param sm The owning entity's state manager. */ public void removeOwnedEntitiesStateManagers(Collection<OpenJPAStateManager> stateManagersToRestore, OpenJPAStateManager sm) { // Remove the state manager from objects in relation for making their serialization not // handled by OpenJPA which can cause a deadlock when writing to space. // The deadlock is caused because when serializing a monitored instance, OpenJPA takes over // and attempts to access an already locked layer in OpenJPA's hierarchy which causes // a deadlock. for (FieldMetaData fmd : sm.getMetaData().getFields()) { if (!sm.getLoaded().get(fmd.getIndex())) continue; if (fmd.isEmbeddedPC()) { Object value = sm.fetch(fmd.getDeclaredIndex()); if (value != null) { PersistenceCapable pc = (PersistenceCapable) value; OpenJPAStateManager stateManager = (OpenJPAStateManager) pc.pcGetStateManager(); removeOwnedEntitiesStateManagers(stateManagersToRestore, stateManager); pc.pcReplaceStateManager(null); stateManagersToRestore.add(stateManager); } } else if (fmd.getAssociationType() == FieldMetaData.ONE_TO_MANY || isPersistentCollection(fmd)) { Collection<?> collection = (Collection<?>) sm.fetch(fmd.getIndex()); if (collection != null) { for (Object item : collection) { PersistenceCapable pc = (PersistenceCapable) item; OpenJPAStateManager stateManager = (OpenJPAStateManager) pc.pcGetStateManager(); if (stateManager != null) { // Set relationship owner setOwnerStateManagerForPersistentInstance(item, sm, fmd); removeOwnedEntitiesStateManagers(stateManagersToRestore, stateManager); stateManagersToRestore.add(stateManager); pc.pcReplaceStateManager(null); } } } } else if (fmd.getAssociationType() == FieldMetaData.ONE_TO_ONE) { Object value = sm.fetch(fmd.getIndex()); if (value != null) { setOwnerStateManagerForPersistentInstance(value, sm, fmd); PersistenceCapable pc = (PersistenceCapable) value; OpenJPAStateManager stateManager = (OpenJPAStateManager) pc.pcGetStateManager(); removeOwnedEntitiesStateManagers(stateManagersToRestore, stateManager); stateManagersToRestore.add(stateManager); pc.pcReplaceStateManager(null); } } } } /** * @return whether the provided field meta data describes a persistent collection which is not a one to many relationship. * usually declared with the PersistentCollection annotation. */ private boolean isPersistentCollection(FieldMetaData fmd) { return Collection.class.isAssignableFrom(fmd.getDeclaredType()) && fmd.getElement() != null && PersistenceCapable.class.isAssignableFrom(fmd.getElement().getDeclaredType()); } /** * Sets the provided state manager as the managed object's owner. * @param managedObject The managed object to set the owner for. * @param sm The owner's state manager. */ public void setOwnerStateManagerForPersistentInstance(Object managedObject, OpenJPAStateManager sm, FieldMetaData fmd) { StateManager stateManager = (StateManager)((PersistenceCapable) managedObject).pcGetStateManager(); if (stateManager == null) throw new IllegalStateException("Attempted to set an Owner back-reference for an unmanaged instance: " + managedObject.toString() + " of type: " + managedObject.getClass().getName()); stateManager.setOwnerInformation((StateManager) sm, fmd); } /** * Sets the provided state manager as the owner for the provided field value. * @param sm The owner's state manager. * @param fmd The field's value the owner will be set for. */ public void initializeOwnerReferencesForField(StateManager sm, FieldMetaData fmd) { if (fmd.getAssociationType() == FieldMetaData.ONE_TO_MANY) { Collection<?> collection = (Collection<?>) sm.fetch(fmd.getIndex()); if (collection != null) { for (Object item : collection) { if (item != null) { _relationsManager.setOwnerStateManagerForPersistentInstance(item, sm, fmd); } } } } else if (fmd.getAssociationType() == FieldMetaData.ONE_TO_ONE || fmd.isEmbeddedPC()) { Object value = sm.fetch(fmd.getIndex()); if (value != null) { _relationsManager.setOwnerStateManagerForPersistentInstance(value, sm, fmd); } } } /** * Attempts to find the super-owner of the provided state manager in a relationship to update. * Throws an exception if such a state manager doesn't exist. * @param sm The owned relationship state manager. * @return The super-owner state manager of the relationship. */ public FieldOwnerInformation getStateManagerToUpdate(StateManager sm) { final Integer associationType = _classesRelationStatus.get(sm.getMetaData().getDescribedType()); if (associationType == null) throw new IllegalStateException("Error updating: " + sm.getMetaData().getClass().getName() + " with id: " + sm.getId()); final StateManager ownerStateManager = sm.getOwnerStateManager(); if (ownerStateManager != null) { if (associationType == FieldMetaData.ONE_TO_MANY) { for (FieldMetaData fmd : ownerStateManager.getMetaData().getFields()) { if (fmd.getElement().getDeclaredType().equals(sm.getMetaData().getDescribedType())) { Collection<?> collection = (Collection<?>) ownerStateManager.fetch(fmd.getIndex()); if (collection == null || !collection.contains(sm.getManagedInstance())) break; if (ownerStateManager.getOwnerStateManager() != null) return getStateManagerToUpdate(ownerStateManager); return new FieldOwnerInformation(ownerStateManager, fmd); } } } else if (associationType == FieldMetaData.ONE_TO_ONE) { for (FieldMetaData fmd : ownerStateManager.getMetaData().getFields()) { if (fmd.getDeclaredType().equals(sm.getMetaData().getDescribedType())) { Object value = ownerStateManager.fetch(fmd.getIndex()); if (value == null || !value.equals(sm.getManagedInstance())) break; if (ownerStateManager.getOwnerStateManager() != null) return getStateManagerToUpdate(ownerStateManager); return new FieldOwnerInformation(ownerStateManager, fmd); } } } } throw new IllegalStateException("Attempted to update an owned entity: " + sm.getMetaData().getClass().getName() + " with Id: " + sm.getId() + " which has no owner."); } /** * Restores state managers for the provided collection of state managers. * @param stateManagersToRestore State managers collection to restore. */ public void restoreRemovedStateManagers(Collection<OpenJPAStateManager> stateManagersToRestore) { for (OpenJPAStateManager sm : stateManagersToRestore) { sm.getPersistenceCapable().pcReplaceStateManager(sm); } } /** * Collects information on current OpenJPA listed class meta data list. * On every call to flush() the method is called & checks if there are new classes to initialize. */ public synchronized void initializeClassesRelationStatus() { if (!shouldInitializeClassesRelationStatus()) return; // Collect information regarding relationships. // Eventually classes which are in a relation should not be saved to the space // since we only support owned relationships and these instances will be saved as nested instances // of their owning instance. ClassMetaData[] cms = getConfiguration().getMetaDataRepositoryInstance().getMetaDatas(); for (ClassMetaData cm : cms) { // Process class if (!_processedClasses.contains(cm.getDescribedType())) { for (FieldMetaData fmd : cm.getFields()) { if (fmd.getAssociationType() == FieldMetaData.ONE_TO_ONE) { if (!_classesRelationStatus.containsKey(fmd.getDeclaredType())) { _classesRelationStatus.put(fmd.getDeclaredType(), FieldMetaData.ONE_TO_ONE); } } else if (fmd.getAssociationType() == FieldMetaData.ONE_TO_MANY) { if (!_classesRelationStatus.containsKey(fmd.getDeclaredType())) { _classesRelationStatus.put(fmd.getElement().getDeclaredType(), FieldMetaData.ONE_TO_MANY); } } else if (fmd.getAssociationType() == FieldMetaData.MANY_TO_MANY) { throw new IllegalArgumentException("Many-to-many is not supported."); } } validateClassAnnotations(cm.getDescribedType()); _processedClasses.add(cm.getDescribedType()); } } } /** * Gets whether classes relations status is not complete and should be synchronized. * OpenJPA creates class meta data only after an entity is persisted for the first time. */ public boolean shouldInitializeClassesRelationStatus() { return getConfiguration().getMetaDataRepositoryInstance().getMetaDatas().length != _processedClasses.size(); } /** * Attempts to find the instance represented by the provided state manager in the provided * relationships tree. * @param sm The state manager holding the instance to find. * @param sms The state managers which potentially holds the instance to find. * @return The found instance, otherwise null. */ public IEntryPacket findObjectInEntry(StateManager sm, IEntryPacket entry, Stack<StateManager> sms) { final StateManager tempStateManager = sms.pop(); final SpaceTypeInfo ownedTypeInfo = SpaceTypeInfoRepository.getTypeInfo(tempStateManager.getMetaData().getDescribedType()); final FieldMetaData[] fms = sm.getMetaData().getFields(); final FieldMetaData fmd = tempStateManager.getOwnerMetaData(); if (fmd == null) throw new IllegalStateException("Owner field meta data is not set for entity of type: " + tempStateManager.getMetaData().getDescribedType().getName() + " with Id: " + tempStateManager.getId()); // Find space property index (skip version fields..) int spacePropertyIndex = -1; for (int i = 0; i <= fmd.getIndex(); i++) { if (fms[i].isVersion()) continue; spacePropertyIndex++; } // One-to-many if (fmd.getAssociationType() == FieldMetaData.ONE_TO_MANY) { final Object id = ApplicationIds.toPKValues(tempStateManager.getId(), tempStateManager.getMetaData())[0]; final Collection<?> values = (Collection<?>) entry.getFieldValue(spacePropertyIndex); if (values != null) { for (Object item : values) { Object itemId = ownedTypeInfo.getIdProperty().getValue(item); if (id.equals(itemId)) { final IEntryPacket entryPacket = getEntryPacketFromEntity(item); return (sms.isEmpty()) ? entryPacket : findObjectInEntry(tempStateManager, entryPacket, sms); } } } // One-to-one } else if (fmd.getAssociationType() == FieldMetaData.ONE_TO_ONE) { final Object id = ApplicationIds.toPKValues(tempStateManager.getId(), tempStateManager.getMetaData())[0]; final Object value = entry.getFieldValue(spacePropertyIndex); if (value != null) { Object objectId = ownedTypeInfo.getIdProperty().getValue(value); if (id.equals(objectId)) { final IEntryPacket entryPacket = getEntryPacketFromEntity(value); return (sms.isEmpty()) ? entryPacket : findObjectInEntry(tempStateManager, entryPacket, sms); } } // Embedded } else if (fmd.isEmbeddedPC()) { final Object value = entry.getFieldValue(spacePropertyIndex); final IEntryPacket entryPacket = getEntryPacketFromEntity(value); return (sms.isEmpty()) ? entryPacket : findObjectInEntry(tempStateManager, entryPacket, sms); } // Object not found.. return null; } } }